package com.tang.servlet;

import com.tang.annotaions.*;
import com.tang.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class DispatcherServlet extends HttpServlet {
	private Properties properties = new Properties();
	/**
	 * 存放类全限定名
	 */
	private List<String> classList =  new ArrayList<>();

	/**
	 * 存放ioc注入
	 */
	private Map<String, Object> iocMaps = new HashMap<>();

	private List<Handler> handlerList = new ArrayList<>();

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		String contextConfigLocation = config.getInitParameter("contextConfigLocation");
		//1.加载配置文件
		doLoadConfig(contextConfigLocation);

		//2.扫描类 扫描注解
		doScan(properties.getProperty("scanPackage"));
		//3.初始化bean对象，基于注解
		doInitBean();

		//4.实现依赖注入
		doAutowired();
		//5.构建一个handler映射器
		initHandlerMapping();

		System.out.println("springmvc 初始化 完成！！");
	}

	private void initHandlerMapping() {
		if (iocMaps.isEmpty()) {
			return;
		}
		iocMaps.values().forEach(clazz -> {
			AtomicReference<String> url = new AtomicReference<>("");
			if (clazz.getClass().isAnnotationPresent(Controller.class) && clazz.getClass()
					.isAnnotationPresent(RequestMapping.class)) {
				String baseUrl = clazz.getClass().getAnnotation(RequestMapping.class).value();
				url.set(baseUrl);
				Method[] methods = clazz.getClass().getMethods();
				Stream.of(methods).forEach(method -> {
					String methodUrl = method.getAnnotation(RequestMapping.class).value();
					if (method.isAnnotationPresent(RequestMapping.class)) {
						url.set(baseUrl + methodUrl);
					}
					Handler handler = new Handler(clazz, method, Pattern.compile(String.valueOf(url)));
					Parameter[] parameters = method.getParameters();
					for (int i = 0; i < parameters.length; i++) {
						Class<?> type = parameters[i].getType();
						if (type.equals(HttpServletRequest.class) || type.equals(HttpServletResponse.class)) {
							handler.getParameterMapping().put(type.getSimpleName(), i);
						}else {
							handler.getParameterMapping().put(type.getName(), i);
						}
					}

					handlerList.add(handler);
				});
			}
		});
	}

	private void doAutowired() {
		if (iocMaps.isEmpty()) {
			return;
		}
		iocMaps.values().forEach(clazz -> {
			Field[] fields = clazz.getClass().getDeclaredFields();
			Stream.of(fields).forEach(field -> {
				if (field.isAnnotationPresent(Autowired.class)) {
					String name = field.getAnnotation(Autowired.class).value();
					if ("".equals(name.trim())) {
						name = field.getType().getName();
					}
					field.setAccessible(true);
					try {
						field.set(clazz, iocMaps.get(name));
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
				}
			});
		});
	}

	private void doInitBean() {
		if (classList.isEmpty()) {
			return;
		}
		classList.forEach(name -> {
			try {
				Class<?> aClass = Class.forName(name);
				if (aClass.isAnnotationPresent(Controller.class)) {
					String id = lowerFirst(aClass.getSimpleName());
					iocMaps.put(id, aClass.newInstance());
				}else if (aClass.isAnnotationPresent(Service.class)) {
					String value = aClass.getAnnotation(Service.class).value();
					if ("".equals(value.trim())) {
						iocMaps.put(lowerFirst(aClass.getSimpleName()), aClass.newInstance());
					}else {
						iocMaps.put(value, aClass.newInstance());
					}
					Class<?>[] interfaces = aClass.getInterfaces();
					for (Class<?> i : interfaces) {
						iocMaps.put(i.getName(), aClass.newInstance());
					}

				}else {
					//todo 权限
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}

	private String lowerFirst(String simpleName) {
		String first = String.valueOf(simpleName.charAt(0)).toLowerCase();
		return first + simpleName.substring(1);
	}

	private void doScan(String scanPackage) {
		String s = scanPackage.replaceAll("\\.", "/");
		String path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + s;
		File[] files = new File(path).listFiles();
		for (File file : files) {
			String name = file.getName();
			if (file.isDirectory()) {
				doScan(scanPackage + "." + name);
			}else if (name.endsWith(".class")) {
				String className = scanPackage + "." + name.replace(".class", "");
				classList.add(className);
			}
		}
	}

	private void doLoadConfig(String contextConfigLocation) {
		InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
		try {
			properties.load(inputStream);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		Handler handler = getHandler(req);
		if(handler == null) {
			resp.getWriter().write("404 not found");
			return;
		}
		security(req, resp);

// 参数绑定// 获取所有参数类型数组，这个数组的⻓度就是我们最后要传⼊的args数组的⻓度
		Class<?>[] parameterTypes =
				handler.getMethod().getParameterTypes();
// 根据上述数组⻓度创建⼀个新的数组（参数数组，是要传⼊反射调⽤的）
		Object[] paraValues = new Object[parameterTypes.length];
// 以下就是为了向参数数组中塞值，⽽且还得保证参数的顺序和⽅法中形参顺序⼀致
		Map<String, String[]> parameterMap = req.getParameterMap();
// 遍历request中所有参数 （填充除了request， response之外的参数）
		for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
// name=1&name=2 name [1,2]

			String value = String.join((CharSequence) Arrays.asList(param.getValue()), ","); // 如
// 如果参数和⽅法中的参数匹配上了，填充数据
			if(!handler.getParameterMapping().containsKey(param.getKey()))
			{continue;}
// ⽅法形参确实有该参数，找到它的索引位置，对应的把参数值放⼊paraValues
			Integer index =
					handler.getParameterMapping().get(param.getKey());//name在第 2 个位置
			paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置
		}
		int requestIndex =
				handler.getParameterMapping().get(HttpServletRequest.class.getSimpleName(
				)); // 0
		paraValues[requestIndex] = req;
		int responseIndex =
				handler.getParameterMapping().get(HttpServletResponse.class.getSimpleName
						()); // 1
		paraValues[responseIndex] = resp;
// 最终调⽤handler的method属性
		try {
			handler.getMethod().invoke(handler.getController(),paraValues);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	private void security(HttpServletRequest req, HttpServletResponse response) {
		String username = req.getParameter("username");
		iocMaps.values().forEach(clazz -> {
			Class<?> aClass = clazz.getClass();
			if (aClass.isAnnotationPresent(Controller.class) && aClass.isAnnotationPresent(Security.class)){
				String[] securityArray = aClass.getAnnotation(Security.class).value();
				List<String> strings = Arrays.asList(securityArray);
				extracted(response,username,strings);
			}
			Method[] methods = aClass.getMethods();
			Stream.of(methods).forEach(method -> {
				if (method.isAnnotationPresent(Security.class)) {
					extracted(response,username,Arrays.asList(method.getAnnotation(Security.class).value()));
				}
			});

		});
	}

	private void extracted(HttpServletResponse response, String username, List<String> strings) {
		if (!strings.contains(username)) {
			response.setStatus(403);
			try {
				response.getWriter().println("你当前没有权限访问");
				return ;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private Handler getHandler(HttpServletRequest req) {
		if(handlerList.isEmpty()){return null;}
		String url = req.getRequestURI();
		for(Handler handler: handlerList) {
			Matcher matcher = handler.getPattern().matcher(url);
			if(!matcher.matches()){continue;}
			return handler;
		}
		return null;
	}

}
